/***
 * JAVANAISE Implementation
 * JvnServerImpl class
 * Contact: 
 *
 * Authors: 
 */

package jvn.client;

import java.io.Serializable;
import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;

import jvn.client.interfaces.JvnLocalServerI;
import jvn.client.interfaces.JvnObjectI;
import jvn.client.interfaces.JvnRemoteServerI;
import jvn.coord.interfaces.JvnRemoteCoordI;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

public class JvnServerImpl extends UnicastRemoteObject implements JvnLocalServerI, JvnRemoteServerI {

	private static final long serialVersionUID = 1L;

	// A JVN server is managed as a singleton
	private static JvnServerImpl js = null;

	private static String ipServeur = "127.0.0.1";

	private static String nomService = "coordJavanaise";

	private JvnRemoteCoordI jvnRemoteCoord = null;
	private Registry registry = null;

	final static private Logger logger = Logger.getLogger(JvnServerImpl.class);

	Map<Integer, JvnObjectI> jvnObjects = new HashMap<Integer, JvnObjectI>();

	private Registry getRegistry() {
		if (this.registry == null) {
			try {
				this.registry = LocateRegistry.getRegistry(JvnServerImpl.ipServeur);
			} catch (RemoteException e) {
				logger.error("Can't reach coordinateur", e);
			}
		}
		return this.registry;
	}

	protected JvnRemoteCoordI getCoord() {
		if (this.jvnRemoteCoord == null) {
			Registry registry = this.getRegistry();
			try {
				this.jvnRemoteCoord = (JvnRemoteCoordI) registry.lookup(JvnServerImpl.nomService);
			} catch (AccessException e) {
				logger.warn(e);
			} catch (RemoteException e) {
				logger.warn(e);
			} catch (NotBoundException e) {
				logger.warn(e);
			}
		}
		return this.jvnRemoteCoord;
	}

	/**
	 * Default constructor
	 * 
	 * @throws RemoteException
	 * 
	 * @throws JvnException
	 **/
	private JvnServerImpl() throws RemoteException {
		super();

	}

	@SuppressWarnings("unchecked")
	protected static Class<Object> load(ClassLoader loader, String NAME) {
		try {
			return (Class<Object>) loader.loadClass("grapher.Model.fc." + NAME);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("unable to load class");
		}
	}

	/**
	 * Static method allowing an application to get a reference to a JVN server instance
	 * 
	 * @throws JvnException
	 **/
	public static JvnServerImpl jvnGetServer() {
		BasicConfigurator.configure();

		if (js == null) {
			try {
				js = new JvnServerImpl();
			} catch (Exception e) {
				logger.warn(e);
				return null;
			}
		}
		return js;
	}

	/**
	 * The JVN service is not used anymore
	 * 
	 * @throws JvnException
	 **/
	public synchronized void jvnTerminate() throws jvn.client.JvnException {
		try {
			// on synchronise les objet chez le client,
			// car si le coord le faisait, il est possible que le client ai déja terminé
			// avant que la synchro des objet ai fini.
			for (JvnObjectI jvnObject : jvnObjects.values()) {
				jvnObject.jvnInvalidateWriter();
			}
			getCoord().jvnTerminate(this);
		} catch (RemoteException e) {
			logger.warn("Cannot say to server 'terminate'", e);
		}
	}

	/**
	 * creation of a JVN object, have a write lock
	 * 
	 * @param o
	 *            : the JVN object state
	 * @throws JvnException
	 **/
	public synchronized JvnObjectI jvnCreateObject(Serializable o) throws jvn.client.JvnException {

		int jid = -1;
		try {
			jid = getCoord().jvnGetObjectId();
		} catch (RemoteException e) {
			logger.warn(e);
		}

		JvnObjectImpl jvnObject = new JvnObjectImpl(this.getCoord(), jid, this);
		jvnObject.setObject(o);
		return jvnObject;
	}

	public synchronized int getJid() throws jvn.client.JvnException {
		int jid = -1;
		try {
			jid = getCoord().jvnGetObjectId();
		} catch (RemoteException e) {
			logger.warn(e);
		}
		return jid;
	}

	/**
	 * Associate a symbolic name with a JVN object
	 * 
	 * @param jon
	 *            : the JVN object name
	 * @param jo
	 *            : the JVN object
	 * @throws JvnException
	 **/
	public synchronized void jvnRegisterObject(String jon, JvnObjectI jo) throws jvn.client.JvnException {
		try {
			jvnObjects.put(jo.jvnGetObjectId(), jo);
			getCoord().jvnRegisterObject(jon, jo, js);
		} catch (RemoteException e) {
			logger.warn(e);
		}
	}

	/**
	 * Provide the reference of a JVN object beeing given its symbolic name
	 * 
	 * @param jon
	 *            : the JVN object name
	 * @return the JVN object
	 * @throws JvnException
	 **/
	@Override
	public synchronized JvnObjectI jvnLookupObject(String jon) throws jvn.client.JvnException {
		try {
			JvnObjectI a = getCoord().jvnLookupObject(jon, this);
			if (a != null) {
				JvnObjectImpl b = new JvnObjectImpl(this.jvnRemoteCoord, a, this);
				jvnObjects.put(b.jvnGetObjectId(), b);
				return b;
			} else {
				return null;
			}
		} catch (RemoteException e) {
			logger.warn(e);
		}
		return null;
	}

	// /**
	// * Get a Read lock on a JVN object
	// *
	// * @param joi
	// * : the JVN object identification
	// * @return the current JVN object state
	// * @throws JvnException
	// **/
	// public Serializable jvnLockRead(int joi) throws JvnException {
	// JvnObjectI jvnO = this.jvnObjects.get(joi);
	// jvnO.jvnLockRead();
	// return jvnO.jvnGetObjectState();
	// }

	// /**
	// * Get a Write lock on a JVN object
	// *
	// * @param joi
	// * : the JVN object identification
	// * @return the current JVN object state
	// * @throws JvnException
	// **/
	// public Serializable jvnLockWrite(int joi) throws JvnException {
	// JvnObjectI jvnO = this.jvnObjects.get(joi);
	// jvnO.jvnLockWrite();
	// return jvnO.jvnGetObjectState();
	// }

	/**
	 * Invalidate the Read lock of the JVN object identified by id called by the JvnCoord
	 * 
	 * @param joi
	 *            : the JVN object id
	 * @return void
	 * @throws java.rmi.RemoteException
	 *             ,JvnException
	 **/
	public void jvnInvalidateReader(int joi) throws java.rmi.RemoteException, jvn.client.JvnException {
		this.jvnObjects.get(joi).jvnInvalidateReader();
	}

	/**
	 * Invalidate the Write lock of the JVN object identified by id
	 * 
	 * @param joi
	 *            : the JVN object id
	 * @return the current JVN object state
	 * @throws java.rmi.RemoteException
	 *             ,JvnException
	 **/
	public Serializable jvnInvalidateWriter(int joi) throws java.rmi.RemoteException, jvn.client.JvnException {
		JvnObjectI jvnO = this.jvnObjects.get(joi);
		jvnO.jvnInvalidateWriter();
		return jvnO.jvnGetObjectState();
	};

	/**
	 * Reduce the Write lock of the JVN object identified by id
	 * 
	 * @param joi
	 *            : the JVN object id
	 * @return the current JVN object state
	 * @throws java.rmi.RemoteException
	 *             ,JvnException
	 **/
	public Serializable jvnInvalidateWriterForReader(int joi) throws java.rmi.RemoteException, jvn.client.JvnException {
		JvnObjectI jvnO = this.jvnObjects.get(joi);
		jvnO.jvnInvalidateWriterForReader();
		return jvnO.jvnGetObjectState();
	}

	/**
	 * initialise le coordinateur dans un état identique a celui après son instanciation.
	 */
	public void reinitialize() {
		jvnObjects.clear();
		try {
			getCoord().reinitialize();
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (JvnException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	};

}
